package zdream.control.world;

import zdream.utils.math.MathUtils;
import zdream.utils.math.Rectangle;

/**
 * @version 0.0.1
 *   该版本之后规定, box 的 x, y, width, height 的参数的最小分辨率是 1/256
 *   也就是说 x, y, width, height 必须是 1/256 的整数倍,
 *   防止出现 4.000001 不等于 4 这种浮点计算错误情况
 *
 * @author Zdream
 * @since 0.0.1
 * @date 2022-05-28 (last modified)
 */
public class Box {

	/**
	 * <p>原本每个盒子都是透明体. 这个参数
	 * <p>这里可以为每个盒子设置, 对哪些 camp 是强实体 (1), 弱实体 (2), 向上平台 (16), 向下平台 (32).
	 * 强实体不会被其它平台、实体等拖动,
	 * 弱实体可以踩在平台、实体, 跟着平台、实体移动.
	 * 向上平台正着的角色可以站, 向下平台反着的角色可以站.
	 * <p>需要强调的是, 底下的 Foe 想让上面站的 Foe 被传动 (拖动), 底下的 Foe 必须是 强实体/弱实体/平台
	 * <p>key: camp, value: 地形体. 48 是既是向上平台又是向下平台. (16 & 32 = 48)
	 * </p>
	 */
	public static final int BOX_TERRAIN_STRONG_SOLID = 1; // 不能重叠, 无法移动
	public static final int BOX_TERRAIN_SOFT_SOLID = 2; // 不能重叠, 但能推动
	public static final int BOX_TERRAIN_PLATFORM_UP = 16;
	public static final int BOX_TERRAIN_PLATFORM_DOWN = 32;

	/**
	 * 取消了碰撞伤害之后, 敌人拦住控制角色就使用这个, 直接让角色无法过身.
	 *
	 * <li>BOX_TERRAIN_WALL_LEFT 只限制其它 MFoe 从左边来的移动.
	 * 从空中落地时碰到这个会推向左边
	 * <li>BOX_TERRAIN_WALL_RIGHT 只限制其它 MFoe 从右边来的移动
	 * 从空中落地时碰到这个会推向右边
	 * <li>两者都有时, 限制其它 MFoe 左右两边方向的移动.
	 * 从空中落地时碰到这个会推向左右两边中较近的一边
	 * </li>
	 */
	public static final int
			BOX_TERRAIN_WALL_LEFT = 64,
			BOX_TERRAIN_WALL_RIGHT = 128;

	public final int[] terrain = new int[] {-1, -1, -1, -1, -1};
	/**
	 * <p>自己属于哪个阵营的. (原来叫 camp)
	 * <p>该属性用于判断 box 之间的碰撞.
	 * <p>一般来说, 0 为无(一般无视所有攻击, 比如墙), 1 为我方, 2 为敌方, 3 无差别攻击方(比如陷阱)
	 * </p>
	 */
	public byte side;
	public static final byte SIDE_NONE = 0;
	public static final byte SIDE_PLAYER = 1;
	public static final byte SIDE_ENEMY = 2;
	public static final byte SIDE_NEUTRAL = 3;
	public static final byte SIDE_OTHER = 4;

	public int terrainBySide(byte side, int defaultTerrain) {
		int terrain = this.terrain[side];
		return terrain == -1 ? defaultTerrain : terrain;
	}

	public void setTerrainWithSide(int terrain, byte side) {
		this.terrain[side] = terrain;
	}

	public void setTerrainWithSides(int terrain, byte... sides) {
		for (byte side : sides) {
			this.terrain[side] = terrain;
		}
	}

	/**
	 * 除了 {@link #SIDE_NONE}
	 */
	public void setTerrainWithAllSides(int terrain) {
		for (byte side = SIDE_PLAYER; side <= SIDE_OTHER; side++) {
			this.terrain[side] = terrain;
		}
	}

	public void clearTerrainWithSide(byte side) {
		this.terrain[side] = -1;
	}

	/*
	 * 静态参数部分
	 */

	/**
	 * 锚点位置. 相对于整个房间的位置.<br>
	 * 单位 : p
	 */
	public float anchorX, anchorY;

	/**
	 * 上一步的锚点位置
	 */
	public float lastAnchorX, lastAnchorY;

	/**
	 * 整个方块相对于锚点的位置.<br>
	 * 一般而言, 除了状态改变使形状改变 (比如大小改变、摔倒等), 该数据才会变化<br>
	 * 单位 : p
	 */
	public float boxX, boxY, boxWidth, boxHeight;

	private boolean dirty = true;
	public final Rectangle pos = new Rectangle();

	public void makeDirty() {
		dirty = true;
	}

	/**
	 * 设置锚点位置
	 */
	public void setAnchor(float x, float y) {
		anchorX = normal(x);
		anchorY = normal(y);
		makeDirty();
	}

	/**
	 * 设置锚点横坐标位置
	 */
	public void setAnchorX(float x) {
		anchorX = normal(x);
		makeDirty();
	}

	/**
	 * 设置锚点纵坐标位置
	 */
	public void setAnchorY(float y) {
		anchorY = normal(y);
		makeDirty();
	}

	/**
	 * 在原有的位置上修改锚点位置, 让其坐标增加 (dx, dy)
	 */
	public void addAnchor(float dx, float dy) {
		anchorX += normal(dx);
		anchorY += normal(dy);
		makeDirty();
	}

	/**
	 * 在原有的位置上修改锚点位置, 让其坐标增加 (dx, 0)
	 */
	public void addAnchorX(float dx) {
		anchorX += normal(dx);
		makeDirty();
	}

	/**
	 * 在原有的位置上修改锚点位置, 让其坐标增加 (0, dy)
	 */
	public void addAnchorY(float dy) {
		anchorY += normal(dy);
		makeDirty();
	}

	/**
	 * 设置的是相对 anchor 的位置
	 */
	public void setBox(float x, float y, float width, float height) {
		boxX = normal(x);
		boxY = normal(y);
		boxWidth = normal(width);
		boxHeight = normal(height);
		makeDirty();
	}

	/**
	 * @return
	 *   中心点横坐标, 单位 p
	 */
	public float getCenterX() {
		flush();
		return pos.x + pos.width / 2;
	}

	/**
	 * @return
	 *   中心点纵坐标, 单位 p
	 */
	public float getCenterY() {
		flush();
		return pos.y + pos.height / 2;
	}

	public Box flush() {
		if (!dirty) {
			return this;
		}

		pos.width = boxWidth;
		pos.height = boxHeight;
		pos.x = boxX + anchorX;
		pos.y = boxY + anchorY;
		dirty = false;
		return this;
	}

	/*
	 * 动态参数部分
	 */

	/**
	 * <p>该帧的时间内, 物体移动的长度或趋势.
	 * <p>注意, velocity 里面的值在有些情况下不等于本步物体的位置减去上一步物体的位置,
	 * 大概有以下几种情况:
	 * <li>在斜坡上行走的 (纵向的速度将不会写进 velocity)
	 * <li>在左右移动后撞墙的 (velocity 里面是不判断撞墙时的值)
	 * </li>
	 * </p>
	 */
	public float velocityX, velocityY;

	/**
	 * 设置速度
	 */
	public void setVelocity(float vx, float vy) {
		this.velocityX = vx;
		this.velocityY = vy;
	}
	public void setVelocityX(float vx) {
		this.velocityX = vx;
	}
	public void setVelocityY(float vy) {
		this.velocityY = vy;
	}

	/**
	 * 按照速度, 本帧结束后 anchor 会增减 velocityX 和 velocityY
	 */
	public void updateAnchor() {
		this.addAnchor(velocityX, velocityY);
	}

	public void updateAnchorX() {
		this.addAnchorX(velocityX);
	}

	public void updateAnchorY() {
		this.addAnchorY(velocityY);
	}

	/*
	 * 配置参数
	 */
	/**
	 * 受到的重力及其它环境力的合力, 是重力的几倍;
	 * 受到相当于一倍重力的合力影响, 方向同 gravityDown 时, 参数为 1; 不受合力影响时为 0
	 */
	public float gravityScale = 1;
	/**
	 * 当在空中下落到一段时间后, 上下的速度不再变化, 这个值就是该速度;
	 * 正为向上
	 */
	public float gravityVelocity = -28;
	/**
	 * 受到的重力是否向下, 这里不计其它力.
	 * 比较实际的意义是判断角色的纹理是正放还是倒放
	 */
	public boolean gravityDown = true;
	/**
	 * 左右朝向. true 为右
	 */
	public boolean orientation;

	/*
	 * 状态参数
	 */
	/**
	 * 四面是否碰到边了. 不一定是地形块, 还有可能是其它 foe 类型的砖块
	 */
	public boolean leftTouched, rightTouched, topTouched, bottomTouched;

	/**
	 * 是否在空中, false 表示在地上 / 平台上
	 */
	public boolean inAir;

	/* **********
	 * 通用方法 *
	 ********** */

	/**
	 * 规格化, p 的最小单位设置为 1/256 的整数倍
	 */
	public static float normal(float p) {
		return MathUtils.floor(p * 256 + 0.5f) / 256f;
	}

	/**
	 * 点转换成格子. 如果点刚好是整数, 算到靠右的格子上
	 * <li>输入 2.4 返回 2
	 * <li>输入 0.4 返回 0
	 * <li>输入 2 返回 2
	 * <li>输入 0 返回 0
	 * <li>输入 -0.5 返回 -1
	 * </li>
	 */
	public static int pToBlock(float p) {
		return MathUtils.floor(p);
	}

	/**
	 * 点转换成格子. 如果点刚好是整数, 算到靠左的格子上
	 * <li>输入 2.4 返回 2
	 * <li>输入 0.4 返回 0
	 * <li>输入 2 返回 1
	 * <li>输入 0 返回 -1
	 * <li>输入 -0.5 返回 -1
	 * </li>
	 */
	public static int pToBlockLeft(float p) {
		int b = MathUtils.floor(p);
		return b == p ? b - 1 : b;
	}
}
